package cz.pecinovsky.česky.oopnz._134_;
//Příliš žluťoučký kůň úpěl ďábelské ódy

import cz.pecinovsky.česky.oopnz.správce.Řadič;
import cz.pecinovsky.česky.oopnz.správce.SprávcePlátna;

//import cz.pecinovsky.česky.oopnz.utility.IO;


//import static cz.pecinovsky.česky.oopnz._134_.Auto_134_         .Auto;
import static cz.pecinovsky.česky.oopnz._134_.Okruh_134_        .Okruh;
import static cz.pecinovsky.česky.oopnz._134_.TovárnaNaKola_140_.TovárnaNaKola;
import static cz.pecinovsky.česky.oopnz._134_.ITovárnaOkruhů_140_.ITovárnaOkruhů;
import static cz.pecinovsky.česky.oopnz._134_.IZávod_138_       .IZávod;
import static cz.pecinovsky.česky.oopnz._134_.IZávodník_137_    .IZávodník;
import static cz.pecinovsky.česky.oopnz._134_.PoleCesty_134_    .PoleCesty;
import static cz.pecinovsky.česky.oopnz._134_.Šipka_134_        .Šipka;
import static cz.pecinovsky.česky.oopnz._134_.Vozidlo_B_137_    .Vozidlo_B;


//import static cz.pecinovsky.česky.oopnz._134_.Závod_T_141_      .Závod_T;
////////////////////////////////////////////////////////////////////////////////
//%%+  +++++ Konec ignorovaného počátečního textu +++++
//%I+
//import cz.pecinovsky.česky.oopnz.správceplátna.IOvládaný;
//import cz.pecinovsky.česky.oopnz.správceplátna.Řadič;
//import cz.pecinovsky.česky.oopnz.správceplátna.SprávcePlátna;
//%I-

import cz.pecinovsky.česky.oopnz.utility.Barva;
import cz.pecinovsky.česky.oopnz.utility.IO;
import cz.pecinovsky.česky.oopnz.utility.Pozice;
import cz.pecinovsky.česky.oopnz.utility.Rozměr;
import cz.pecinovsky.česky.oopnz.utility.Směr8;

//%A+ >142
import java.util.ArrayList;
//%A-
import java.util.Collection;
//%A+ >142
import java.util.Collections;
import java.util.Comparator;
//%A-
import java.util.HashMap;
//%A+ >142
import java.util.List;
//%A-
import java.util.Map;
//%X+ xxxxx Začátek přeskakovaného textu xxxxx
/*******************************************************************************
 *<pre>
 * Předchozí:   Vychází ze třídy Závod_K
 * Následující: Prozatím není
 *
 * Projekt  140
 *   + Přidáno
 *   - Odebráno
 *   ~ připravDalšíOkruh - u prvního vytvořeného okruhu se zeptá na jeho šířku
 *</pre>
 */
public class Závod_T_141_
{    private Závod_T_141_() {}   static
////////////////////////////////////////////////////////////////////////////////
//%X- ----- Konec přeskakovaného textu -----


/*******************************************************************************
 * Instance třídy {@code Závod_T} představují závody, které je možno absolvovat,
 * Přičemž okruh, na němž se závod pojede, je vytvořen továrnou dodanou
 * konstruktoru daného závodu jako parametr.
 * Závod je charakterizován okruhem, na kterém se pojede, a počtem přihlášených
 * závodníků. Všichni přihlášení závodníci pak pojedou daný okruh současně
 * každý na své instanci příslušného okruhu.
 *
 * @author Rudolf PECINOVSKÝ
 * @version 6.03.2017 — 2010-11-08
 */
public class Závod_T implements IZávod
{
//== KONSTANTNÍ ATRIBUTY TŘÍDY =================================================

    /** Plátno, na které se bude instance kreslit. */
    private static final SprávcePlátna SP = SprávcePlátna.getInstance();

    /** Implicitní maximální povolená šířka plátna, do nějž se musí vejít
     *  vedle sebe okruhy všech přihlášených závodníků
     *  oddělené navzájem prázdným sloupcem. */
    private static final int IMPLICITNÍ_MAX_ŠÍŘKA_PLÁTNA = 1000;

    /** Implicitní maximální povolená výška plátna,
     *  do níž se musí vejít budovaný okruh. */
    private static final int IMPLICITNÍ_MAX_VÝŠKA_PLÁTNA = 700;




//== PROMĚNNÉ ATRIBUTY TŘÍDY ===================================================

    /** Maximální povolená bodová šířka plátna, do nějž se musí vejít vedle
     *  sebe okruhy všech přihlášených závodníků oddělené navzájem prázdným
     *  sloupcem. Z povolené šířky a výšky plátna bude podle počtu přihlášených
     *  závodníků a rozměru okruhu odvozována optimální velikost modulu. */
    private static int maxŠířkaPlátna = IMPLICITNÍ_MAX_ŠÍŘKA_PLÁTNA;

    /** Maximální povolená bodová výška plátna, do níž se musí vejít budovaný
     *  okruh. Z povolené šířky a výšky plátna bude podle počtu přihlášených
     *  závodníků a rozměru okruhu odvozována optimální velikost modulu. */
    private static int maxVýškaPlátna = IMPLICITNÍ_MAX_VÝŠKA_PLÁTNA;



//== STATICKÝ INICIALIZAČNÍ BLOK - STATICKÝ KONSTRUKTOR ========================
//== KONSTANTNÍ ATRIBUTY INSTANCÍ ==============================================

    /** Mapa přiřazující jednotlivým závodníkům informace,
     * které si o nich instance závodu pamatuje. */
    private final Map<IZávodník, Info> závodníci =
                                       new HashMap<IZávodník, Info>();
//%A+ >142

    /** Seznam, do nějž jsou závodníci ukládáni v pořadí,
     * v němž dojeli do cíle. */
    private final List<Info> dojezdy = new ArrayList<Info>();
//%A-

    /** Počet kol, na která se závod jede. */
    private final int početKol;

    /** Instance vytvářející okruhy, na nichž se závodí. */
    private final ITovárnaOkruhů továrna;



//== PROMĚNNÉ ATRIBUTY INSTANCÍ ================================================

    /** Aktuálně nastavený modul plátna. */
    private int modul;

    /** Systémový čas v okamžiku startu závodu. */
    private long čas0;

    /** Počet doposud registrovaných závodníků a tím i vytvořených okruhů. */
    private int závodníků = 0;

    /** Počet sloupců a řádků, které daný okruh zaujímá -
     *  ovlivňují pozici jednotlivých vytvářených okruhů a velikost plátna. */
    private int sloupcůOkruhu, řádkůOkruhu;



//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ TŘÍDY ========================================
//== OSTATNÍ NESOUKROMÉ METODY TŘÍDY ===========================================

    /***************************************************************************
     * Zapamatuje si maximální povolený rozměr plátna a po přihlášení
     * všech závodníků nastaví modul a rozměry plátna tak, aby se co
     * nejefektivněji využila přidělená plocha.
     *
     * @param šířka Šířka plátna v bodech
     * @param výška Výška plátna v bodech
     */
    public static void setMaxRozměrPlátna(int šířka, int výška)
    {
        Závod_T.maxŠířkaPlátna = šířka;
        Závod_T.maxVýškaPlátna = výška;
    }



//##############################################################################
//== KONSTRUKTORY A TOVÁRNÍ METODY =============================================

    /***************************************************************************
     * Vytvoří instanci schopnou pořádat závody na jedno kolo na okruzích
     * vyráběných zadanou továrnou.
     *
     * @param továrna      Továrna, vytvářející okruhy, na nichž se pojede
     */
    public Závod_T(ITovárnaOkruhů továrna)
    {
        this(továrna, 1);
    }


    /***************************************************************************
     * Vytvoří instanci schopnou pořádat závody na zadaný počet kol
     * na okruzích vyráběných zadanou továrnou.
     *
     * @param továrna      Továrna, vytvářející okruhy, na nichž se pojede
     * @param početKol     Počet kol, na která se závod jede
     */
    public Závod_T(ITovárnaOkruhů továrna, int početKol)
    {
        this.továrna  = továrna;
        this.početKol = početKol;
        this.modul    = SP.getKrok();   //Dočasná hodnota platící jen do startu
    }



//== ABSTRAKTNÍ METODY =========================================================
//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ INSTANCÍ =====================================
//== OSTATNÍ NESOUKROMÉ METODY INSTANCÍ ========================================

    /***************************************************************************
     * Otevře dialogové okno, v němž závodníkům oznámí, že je odstartováno,
     * a poté, co toto okno zavřou, změří systémový čas.
     */
    public void start()
    {
//%A+ >142
        dojezdy.clear();    //V tomto závodě zatím nikdo nedojel
//%A-
        Collection<Info> infa = závodníci.values();
        připravPlátno();  //Upraví jeho velikost tak, aby se okruhy právě vešly
        for (Info info : infa) {
            info.řadič.start();
        }
        IO.zpráva("Po potvrzení tohoto okna se začne měřit čas");
        čas0 = System.currentTimeMillis();
    }


    /***************************************************************************
     * Ukončí závod, čím zruší všechny registrace a připraví se na nové.
     */
    public void stop()
    {
        závodníci.clear();
    }


    /***************************************************************************
     * Zaregistruje zadaného závodníka, umístí jej na start závodu,
     * zjistí si od uživatele potřebné klávesy
     * a přihlásí závodníka jako posluchače klávesnice.
     *
     * @param závodník Závodník registrující se pro daný závod
     */
    @Override
    public void registrace(IZávodník závodník)
    {
        if (závodníci.containsKey(závodník)) {
            IO.zpráva("Závodníka nelze zaregistrovat dvakrát: " + závodník);
            return;
        }
        Okruh     okruh  = připravDalšíOkruh(); //Opraví i atribut závodníků
        PoleCesty start  = okruh.getPočátek();
        PoleCesty cíl    = start.getDalší();
//%I+
//        Řadič     řadič  = Řadič.vytvořPro(závodník);
//%I-
//%X+
        Řadič     řadič  = new Řadič(závodník, Řadič.getImplicitníKlávesy(),
//                                     new int[] {37, 39, 38, 40, 32, 32, 32},
                                     false);
//%X-

//%I+ <142
//        Info info = new Info(okruh, start, cíl, řadič, početKol, závodník);
//%I-
//%A+ >142
        Info info = new Info(okruh, start, cíl, řadič, početKol,
                             závodník, závodníků);
//%A-
        závodníci.put(závodník, info);
    }


    /***************************************************************************
     * Prověří, že závodník dojel do správné průběžné cílové pozice.
     * Pokud dojel, připraví další průběžnou pozici, pokud nedojel, čeká dál.
     *
     * @param závodník Závodník hlásící změnu své pozice
     */
    @Override
    public void průjezdníKontrola(IZávodník závodník)
    {
        Info info = závodníci.get(závodník);
        if (info == null) {
            return;                      //==========>
        }
        Pozice pz = závodník.getPozice();
        Pozice pc = info.cíl.getPozice();
        if (pz.equals(pc)) {
            if (info.cíl == info.start) {
                info.zbýváKol--;
                if (info.zbýváKol == 0) {
                    konecZávodu(závodník, info);
                    return;                  //==========>
                }
            }
            info.cíl = info.cíl.getDalší();
        }
    }


    /***************************************************************************
     * Vrátí řetězec sloužící jako textový "podpis" instance
     * používaný především při ladění.
     */
    @Override
    public String toString()
    {
        return "Závod_T(továrna=" + továrna + ", počet=" + závodníků +
               ", modul=" + modul + ")";
    }



//== SOUKROMÉ A POMOCNÉ METODY TŘÍDY ===========================================
//== SOUKROMÉ A POMOCNÉ METODY INSTANCÍ ========================================

    /***************************************************************************
     * Připraví další okruh a vrátí odkaz na něj.
     *
     * @return Odkaz na připravený okruh
     */
    private Okruh připravDalšíOkruh()
    {
        int x = závodníků * (sloupcůOkruhu + 1) * modul;
        Pozice pozice = new Pozice(x, 0);
        Okruh okruh = továrna.vytvořOkruh(pozice, Barva.getBarva(závodníků));
        závodníků = závodníků + 1;
        if (závodníků == 1) {
            Rozměr rozměrOkruhu = okruh.getPRozměr();
            sloupcůOkruhu = rozměrOkruhu.šířka;
            řádkůOkruhu   = rozměrOkruhu.výška;
        }
        return okruh;
    }


    /***************************************************************************
     * Ukončí závod pro zadaného závodníka.
     *
     * @param závodník Závodník ukončivší závod
     */
    private void konecZávodu(IZávodník závodník, Info info)
    {
        long nyní = System.currentTimeMillis();
        info.řadič.konec();
        int čas = (int)(nyní - čas0 + 50) / 100;
        System.out.println("Závodník " + závodník.getNázev() +
               " absolvoval okruh v čase " + čas/10 + "," + čas%10 + " sekund");
//%A+ >142
        //Zapamatujeme si info závodníka s časem jeho dojezdu
        info.čas = čas;
        dojezdy.add(info);
        info.pořadí = dojezdy.size();
//%A-
        závodníci.remove(závodník);
        if (závodníci.isEmpty()) {
//%I+ <142
//            System.out.println("Konec závodu");
//%I-
//%I+ <0
//            IO.zpráva("Konec závodu" +
//                  "\n\nvýsledky najdete ve standardním výstupu");
//%I-
//%A+ >142
            vyhodnoťZávod();
//%A-
        }
    }


    /***************************************************************************
     * Připraví plátno tak, aby se na něj právě vešly okruhy
     * pro všechny přihlášené závodníky
     */
    private void připravPlátno()
    {
        int  sloupcůPlátna  = (sloupcůOkruhu + 1) * závodníků  -  1;
        int  xKrok = maxŠířkaPlátna / sloupcůPlátna;
        int  yKrok = maxVýškaPlátna / řádkůOkruhu;
        this.modul = Math.min(xKrok, yKrok);
        SP.setKrokRozměr(modul, sloupcůPlátna, řádkůOkruhu);

        SP.nekresli(); {
            Collection<Info> infa = závodníci.values();
            for (Info info : infa) {
                Okruh okruh = info.okruh;
                okruh.setModul(modul);
                SP.přidej(okruh);
                info.závodník.setSměr  (okruh.getPočátek().getSměr());
                info.závodník.setPozice(okruh.getPočátek().getPozice());
                info.závodník.setModul(modul);
            }
        } SP.vraťKresli();
    }

//%A+ >142

    /***************************************************************************
     * Vyhodnotí celý závod, tj. vypíše záznamy jednotlivých závodníků seřazené
     * nejprve podle umístění, pak podle abecedy a nakonec podle okruhů,
     * na nichž závodníci jeli.
     */
    private void vyhodnoťZávod()
    {
        vypišDojezdy("Výsledné pořadí:");

        Collections.sort(dojezdy);
        vypišDojezdy("Výsledky podle abecedy:");

        Collections.sort(dojezdy, new CompDleOkruhů());
        vypišDojezdy("Výsledky podle okruhů:");
    }


    /***************************************************************************
     * Vypíše na standardní výstup zadanou zprávu následovanou informacemi o
     * jednotlivých závodnících v pořadí určeném seznamem {@link #dojezdy}.
     * Každý závodník je vypsán na samostatném řádku a je u něj uvedeno
     * pořadí, v němž dojel, okruh, na němž jel, jeho čas a jméno.
     *
     * @param zpráva Zpráva, která celou listinu uvádí
     */
    private void vypišDojezdy(String zpráva)
    {
        System.out.println("\n" + zpráva);
        for ( Info info : dojezdy) {
            int    pořadí= info.pořadí;
            String jméno = info.závodník.getNázev();
            int    čas   = info.čas;
            int    číslo = info.číslo;
            System.out.println(pořadí + ".  o"  + číslo  + " - " +
                               čas/10 + ","     + čas%10 + " - " +  jméno);
        }
    }

//%A-


//== INTERNÍ DATOVÉ TYPY =======================================================

    /***************************************************************************
     * Interní přepravka obsahující základní informace o závodníkovi
     * a průběžném stavu jeho závodu.
     */
//%I+ <142
//    private static class Info
//%I-
//%A+ >142
    private static class Info implements Comparable<Info>
//%A-
    {
        /** Okruh, na němž je závoděno. */
        private final Okruh okruh;

        /** Kontrolovaný závodník. */
        private final IZávodník závodník;

        /** Řadič, zprostředkovávající ovládání závodníka z klávesnice. */
        private final Řadič řadič;

        /** Pole, z nějž závod odstartuje. */
        private final PoleCesty start;

        /** Příští pole, na které má závodník dojet
         *  a na němž se bude kontrolovat, zda je skutečně projel.  */
        private PoleCesty cíl;

        /** Počet kol zbývajících do cíle. */
        private int zbýváKol;
//%A+ >142

        /** Pořadové číslo závodníka a tím i jeho okruhu. */
        int číslo;

        /** Výsledné pořadí závodníka. */
        int pořadí;

        /** Počet kol zbývajících do cíle. */
        private int čas;
//%A-


        /***********************************************************************
         * Definuje novou přepravku a inicializuje její atributy.
         */
        Info(Okruh okruh, PoleCesty start, PoleCesty cíl, Řadič řadič, int kol,
//%I+ <142
//             IZávodník závodník)
//%I-
//%A+ >142
             IZávodník závodník, int číslo)
//%A-
        {
            this.okruh    = okruh;
            this.start    = start;
            this.cíl      = cíl;
            this.řadič    = řadič;
            this.zbýváKol = kol;
            this.závodník = závodník;
//%A+ >142
            this.číslo    = číslo;
//%A-
        }
//%A+ >142


        /***********************************************************************
         * Porovná jméno závodníka se jménem závodníka, jehož info obdrží jako
         * parametr a vrátí číslo označující, které z nich je "větší".
         *
         * @param o Objekt, s nímž je daný objekt porovnáván
         * @return Záporné číslo, je-li daný objekt menší, nula je-li shodný
         *         a kladné číslo je-li větší než parametr.
         */
        @Override
        public int compareTo(Info o)
        {
//            return this.závodník.getNázev().compareTo(o.závodník.getNázev());
            String mojeJméno = this.závodník.getNázev();
            String jehoJméno = o   .závodník.getNázev();
            return mojeJméno.compareTo(jehoJméno);
        }
//%A-
    }

//%A+ >142

    /***************************************************************************
     * Třída implementující rozhraní {@code Comparator<Info>},
     * kterou potřebujeme k tomu, abychom mohli porovnat dvě infa
     * podle námi zvoleného kritéria - v tomto případě podle okruhu,
     * na němž jel příslušný závodník.
     */
    private static class CompDleOkruhů implements Comparator<Info>
    {
        /***********************************************************************
         * Porovná zadání infa a za větší prohlásí to, jehož okruh
         * má vyšší pořadí (je zobraze víc vpravo).
         *
         * @param i1 První porovnávané info
         * @param i2 Druhé porovnávané info
         * @return Záporné číslo, je-li pořadí okruhu i1 menší než i2,
         *         nula jsou-li pořadí obou okruhů shodná a kladné číslo
         *         je-li pořadí okruhu i1 větší než pořadí okruhu i2
         */
        @Override
        public int compare(Info i1, Info i2)
        {
            return i1.číslo - i2.číslo;
        }
    }

//%A-


//== TESTY A METODA MAIN =======================================================

    /***************************************************************************
     * Testovací metoda.
     */
    public static void test()
    {
        int počet  = 4;
        int rozměr = počet * 100;
        int kol    = 2;
        Závod_T.setMaxRozměrPlátna(rozměr, rozměr);
        ITovárnaOkruhů ito = new TovárnaNaKola(2, 2, Směr8.JIH);
        Závod_T závod = new Závod_T(ito, kol);

        for (int i=1;   i <= počet;   i++) {
            Šipka  šipka = new Šipka(0, 0, Barva.BÍLÁ);
            Vozidlo_B vb = new Vozidlo_B(šipka);
            vb.setNázev(Barva.getBarva(i-1).getNázev());//"Číslo_" + i);
            vb.registrujSePro(závod);
        }
        závod.start();
    }
//    /** @param args Parametry příkazového řádku - nepoužívané. */
//    public static void main( String[] args )  {  test();  }
}

//%%-  ----- Začátek ignorovaného závěrečného textu -----
////////////////////////////////////////////////////////////////////////////////
//== TESTY A METODA MAIN =======================================================

    /***************************************************************************
     * Testovací metoda.
     */
    public static void test()
    {
        Závod_T.test();

//        IO.zpráva("Až se pokocháš, stiskni OK");
//        System.exit(0);
    }
    /** @param args Parametry příkazového řádku - nepoužívané. */
    public static void main( String[] args )  {  test();  }
}
